<template>
  <input
    class="pin-field"
    autocapitalize="none"
    ref="pinInputRef"
    v-for="item in modelValue.length"
    :key="item"
    :class="[
      error && 'is-error',
      disabled && 'is-disabled',
      item - 1 === currentIndex && 'is-focus'
    ]"
    :aria-label="`pin input ${item} of ${modelValue.length}`"
    :type="inputType"
    :autocomplete
    :inputmode="type"
    :pattern
    :readonly
    :placeholder="item - 1 === currentIndex ? '' : placeholder"
    :disabled
    :value="modelValue[item - 1]"
    @focus="handleFocus(item)"
    @blur="handleBlur(item)"
    @input="handleInput"
    @paste="handlePaste"
    @keydown.left.right.home.end="handleKeydown"
    @keydown.backspace="handleBackspace"
    @keydown.delete="handleDelete"
    @compositionstart="handleCompositionStart"
    @compositionend="handleCompositionEnd"
  />
</template>

<script setup lang="ts">
import { computed, ref, unref, useTemplateRef } from 'vue'
import {
  alphanumericRegex,
  nonAlphanumericRegex,
  nonDigitRegex,
  numericRegex
} from '@/utils/modules/regexp'
import type { PinFieldEmits, WrapperProsp } from './types'

const props = withDefaults(defineProps<WrapperProsp>(), {
  modelValue: () => ['', '', '', ''],
  error: false,
  readonly: false,
  size: 'default',
  placeholder: '○',
  disabled: false,
  mask: false,
  otp: false,
  type: 'text'
})

const emits = defineEmits<PinFieldEmits>()

const pinInputRef = useTemplateRef<HTMLInputElement[]>('pinInputRef')
const currentIndex = ref<number | null>(null)

const handleFocus = (item: number) => {
  if (props.readonly) return
  const index = item - 1
  currentIndex.value = index
  emits('focus', index)
}

const handleBlur = (item: number) => {
  if (props.readonly) return
  currentIndex.value = null
  emits('blur', item - 1)
}

const isComposing = ref(false)
const handleCompositionStart = () => {
  isComposing.value = true
}
const handleCompositionEnd = () => {
  isComposing.value = false
}

const handleInput = (e: Event) => {
  const index = unref(currentIndex)
  const pinInputEl = unref(pinInputRef)
  if (index === null || !pinInputEl) return
  const target = e.target as HTMLInputElement
  if (isComposing.value) {
    target.value = ''
    isComposing.value = false
    return
  }
  if (isNumeric.value && !numericRegex.test(target.value)) {
    target.value = target.value.replace(nonDigitRegex, '')
    return
  }
  if (!isNumeric.value && !alphanumericRegex.test(target.value)) {
    target.value = target.value.replace(nonAlphanumericRegex, '')
    return
  }

  target.value = target.value.slice(-1).trim()
  props.modelValue[index] = target.value
  const nextEl = pinInputEl[index + 1]
  if (nextEl) {
    nextEl.focus()
  }
}

const handlePaste = (e: ClipboardEvent) => {
  e.preventDefault()
  const clipboardData = e.clipboardData
  if (!clipboardData) return
  const splitData = clipboardData?.getData('text').split('')
  const values = splitData.filter((item) => item.trim() !== '')
  if (values.length) {
    const index = unref(currentIndex)
    const pinInputEl = unref(pinInputRef)
    if (index === null || !pinInputEl) return
    props.modelValue.forEach((_, index) => {
      props.modelValue[index] = values[index] || ''
    })
    if (values.length < props.modelValue.length) {
      pinInputEl[values.length].focus()
    } else {
      pinInputEl[props.modelValue.length - 1].focus()
    }
  }
}

const handleKeydown = (e: KeyboardEvent) => {
  e.preventDefault()
  const index = unref(currentIndex)
  const pinInputEl = unref(pinInputRef)
  if (index === null || !pinInputEl) return
  const [right, left, home, end] = [
    e.key === 'ArrowRight',
    e.key === 'ArrowLeft',
    e.key === 'Home',
    e.key === 'End'
  ]
  if (right) {
    const nextEl = pinInputEl[index + 1]
    if (nextEl) nextEl.focus()
  }
  if (left) {
    const prevEl = pinInputEl[index - 1]
    if (prevEl) prevEl.focus()
  }
  if (home) pinInputEl[0].focus()
  if (end) pinInputEl[pinInputEl.length - 1].focus()
}

const handleBackspace = (e: KeyboardEvent) => {
  e.preventDefault()
  const index = unref(currentIndex)
  const pinInputEl = unref(pinInputRef)
  if (index === null || !pinInputEl) return
  const target = e.target as HTMLInputElement
  if (target.value) {
    props.modelValue[index] = ''
  } else {
    const prevEl = pinInputEl[index - 1]
    if (prevEl) {
      prevEl.focus()
      props.modelValue[index - 1] = ''
    }
  }
}

const handleDelete = (e: KeyboardEvent) => {
  if (e.key === 'Delete') {
    e.preventDefault()
    const index = unref(currentIndex)
    if (index === null) return
    props.modelValue[index] = ''
  }
}

const inputType = computed(() => {
  const { mask } = props
  if (mask) return 'password'
  return 'text'
})

const autocomplete = computed(() => {
  const { otp } = props
  if (otp) return 'one-time-code'
  return 'off'
})

const pattern = computed(() => {
  const { type } = props
  if (type === 'numeric') return '[0-9]*'
  return undefined
})

const isNumeric = computed(() => {
  const { type } = props
  return type === 'numeric'
})

defineExpose({ el: pinInputRef })

defineOptions({
  name: 'CorePinField'
})
</script>

<style scoped lang="scss">
.pin-field {
  width: calc(var(--el-component-size) - 2px);
  height: calc(100% - 2px);
  padding: 0;
  margin-top: -1px;
  font-size: inherit;
  color: var(--el-text-color-regular);
  text-align: center;
  background-color: var(--el-fill-color-blank);
  border: 1px solid var(--el-border-color);
  transition: border-color var(--el-transition-duration-fast)
    var(--el-transition-function-ease-in-out-bezier);

  &[type='password']::-ms-reveal {
    display: none;
  }

  &[type='password']::-webkit-contacts-auto-fill-button {
    display: none;
  }

  &.is-focus {
    border: 1px solid var(--el-color-primary) !important;
  }

  &.is-error {
    border-color: rgb(var(--core-error-color)) !important;
  }

  &.is-disabled {
    color: var(--el-disabled-text-color);
    cursor: not-allowed;
    background-color: var(--el-disabled-bg-color);
    border-color: var(--el-disabled-border-color) !important;
  }

  &:hover {
    border-color: var(--el-border-color-hover);

    & + .pin-input {
      border-left-color: transparent;
    }
  }

  &:first-child {
    border-radius: var(--el-border-radius-base) 0 0 var(--el-border-radius-base);
  }

  &:not(:last-child) {
    border-right-color: transparent;

    &:hover {
      border-right-color: var(--el-border-color-hover);
    }

    &.is-error {
      border-right-color: transparent !important;
    }

    &.is-disabled {
      border-right-color: transparent !important;
    }
  }

  &:last-child {
    border-radius: 0 var(--el-border-radius-base) var(--el-border-radius-base) 0;
  }

  &::placeholder {
    color: var(--el-text-color-placeholder);
  }
}
</style>
